import cmath from "..";

describe("cmath.vector2", () => {
  describe("add", () => {
    it("should correctly add multiple vectors", () => {
      const v1: cmath.Vector2 = [1, 2];
      const v2: cmath.Vector2 = [3, 4];
      const v3: cmath.Vector2 = [-1, -1];

      const result = cmath.vector2.add(v1, v2, v3);

      expect(result).toEqual([3, 5]); // Expected result: [3, 5]
    });

    it("should return [0, 0] for no input vectors", () => {
      const result = cmath.vector2.add();

      expect(result).toEqual([0, 0]); // Expected result: [0, 0]
    });
  });

  describe("sub", () => {
    it("should correctly subtract one vector from another", () => {
      const v1: cmath.Vector2 = [5, 7];
      const v2: cmath.Vector2 = [2, 3];

      const result = cmath.vector2.sub(v1, v2);

      expect(result).toEqual([3, 4]); // Expected result: [3, 4]
    });

    it("should handle negative results", () => {
      const v1: cmath.Vector2 = [2, 3];
      const v2: cmath.Vector2 = [5, 7];

      const result = cmath.vector2.sub(v1, v2);

      expect(result).toEqual([-3, -4]); // Expected result: [-3, -4]
    });
  });

  describe("multiply", () => {
    it("should correctly multiply two vectors element-wise", () => {
      const v1: cmath.Vector2 = [2, 3];
      const v2: cmath.Vector2 = [4, 5];

      const result = cmath.vector2.multiply(v1, v2);

      expect(result).toEqual([8, 15]); // Expected result: [8, 15]
    });

    it("should return [0, 0] when one vector is [0, 0]", () => {
      const v1: cmath.Vector2 = [2, 3];
      const v2: cmath.Vector2 = [0, 0];

      const result = cmath.vector2.multiply(v1, v2);

      expect(result).toEqual([0, 0]); // Expected result: [0, 0]
    });
  });

  describe("angle", () => {
    it("should calculate the angle for a point in the first quadrant", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [1, 1];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(45); // Expected angle: 45 degrees
    });

    it("should calculate the angle for a point in the second quadrant", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [-1, 1];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(135); // Expected angle: 135 degrees
    });

    it("should calculate the angle for a point in the third quadrant", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [-1, -1];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(225); // Expected angle: 225 degrees
    });

    it("should calculate the angle for a point in the fourth quadrant", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [1, -1];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(315); // Expected angle: 315 degrees
    });

    it("should calculate the angle for a point on the positive x-axis", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [1, 0];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(0); // Expected angle: 0 degrees
    });

    it("should calculate the angle for a point on the negative x-axis", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [-1, 0];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(180); // Expected angle: 180 degrees
    });

    it("should calculate the angle for a point on the positive y-axis", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [0, 1];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(90); // Expected angle: 90 degrees
    });

    it("should calculate the angle for a point on the negative y-axis", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [0, -1];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(270); // Expected angle: 270 degrees
    });

    it("should return 0 for the origin point", () => {
      const origin: cmath.Vector2 = [0, 0];
      const point: cmath.Vector2 = [0, 0];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(0); // Expected angle: 0 degrees
    });

    it("should calculate the angle for a non-origin origin point", () => {
      const origin: cmath.Vector2 = [1, 1];
      const point: cmath.Vector2 = [2, 2];

      const angle = cmath.vector2.angle(origin, point);

      expect(angle).toBeCloseTo(45); // Expected angle: 45 degrees relative to new origin
    });
  });

  describe("rotate", () => {
    it("should rotate a vector by 0 degrees", () => {
      const vector: cmath.Vector2 = [1, 0];
      const rotated = cmath.vector2.rotate(vector, 0);
      expect(rotated[0]).toBeCloseTo(1);
      expect(rotated[1]).toBeCloseTo(0);
    });

    it("should rotate a vector by 90 degrees", () => {
      const vector: cmath.Vector2 = [1, 0];
      const rotated = cmath.vector2.rotate(vector, 90);
      expect(rotated[0]).toBeCloseTo(0);
      expect(rotated[1]).toBeCloseTo(1);
    });

    it("should rotate a vector by 180 degrees", () => {
      const vector: cmath.Vector2 = [1, 0];
      const rotated = cmath.vector2.rotate(vector, 180);
      expect(rotated[0]).toBeCloseTo(-1);
      expect(rotated[1]).toBeCloseTo(0);
    });

    it("should rotate a vector by 270 degrees", () => {
      const vector: cmath.Vector2 = [1, 0];
      const rotated = cmath.vector2.rotate(vector, 270);
      expect(rotated[0]).toBeCloseTo(0);
      expect(rotated[1]).toBeCloseTo(-1);
    });

    it("should rotate a vector by 360 degrees", () => {
      const vector: cmath.Vector2 = [1, 0];
      const rotated = cmath.vector2.rotate(vector, 360);
      expect(rotated[0]).toBeCloseTo(1);
      expect(rotated[1]).toBeCloseTo(0);
    });

    it("should rotate a vector by -90 degrees", () => {
      const vector: cmath.Vector2 = [1, 0];
      const rotated = cmath.vector2.rotate(vector, -90);
      expect(rotated[0]).toBeCloseTo(0);
      expect(rotated[1]).toBeCloseTo(-1);
    });

    it("should rotate a diagonal vector by 45 degrees", () => {
      const vector: cmath.Vector2 = [1, 1];
      const rotated = cmath.vector2.rotate(vector, 45);
      expect(rotated[0]).toBeCloseTo(0);
      expect(rotated[1]).toBeCloseTo(Math.sqrt(2));
    });

    it("should rotate a vector with negative components", () => {
      const vector: cmath.Vector2 = [-1, -1];
      const rotated = cmath.vector2.rotate(vector, 90);
      expect(rotated[0]).toBeCloseTo(1);
      expect(rotated[1]).toBeCloseTo(-1);
    });

    it("should handle a vector with zero components", () => {
      const vector: cmath.Vector2 = [0, 0];
      const rotated = cmath.vector2.rotate(vector, 123); // Any angle
      expect(rotated[0]).toBeCloseTo(0);
      expect(rotated[1]).toBeCloseTo(0);
    });
  });

  describe("intersects", () => {
    it("should return true for overlapping segments", () => {
      const segmentA: cmath.Vector2 = [1, 5];
      const segmentB: cmath.Vector2 = [4, 8];

      const result = cmath.vector2.intersects(segmentA, segmentB);

      expect(result).toBe(true); // Expected: true
    });

    it("should return true for touching segments", () => {
      const segmentA: cmath.Vector2 = [1, 5];
      const segmentB: cmath.Vector2 = [5, 10];

      const result = cmath.vector2.intersects(segmentA, segmentB);

      expect(result).toBe(true); // Expected: true
    });

    it("should return false for non-overlapping segments", () => {
      const segmentA: cmath.Vector2 = [1, 5];
      const segmentB: cmath.Vector2 = [6, 10];

      const result = cmath.vector2.intersects(segmentA, segmentB);

      expect(result).toBe(false); // Expected: false
    });

    it("should return true for segments with zero-length that overlap", () => {
      const segmentA: cmath.Vector2 = [3, 3];
      const segmentB: cmath.Vector2 = [3, 5];

      const result = cmath.vector2.intersects(segmentA, segmentB);

      expect(result).toBe(true); // Expected: true
    });

    it("should return false for zero-length segments that do not overlap", () => {
      const segmentA: cmath.Vector2 = [3, 3];
      const segmentB: cmath.Vector2 = [4, 4];

      const result = cmath.vector2.intersects(segmentA, segmentB);

      expect(result).toBe(false); // Expected: false
    });
  });

  describe("intersection", () => {
    it("should return the intersecting segment for overlapping segments", () => {
      const segmentA: cmath.Vector2 = [1, 5];
      const segmentB: cmath.Vector2 = [3, 7];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toEqual([3, 5]); // Expected: [3, 5]
    });

    it("should return the intersecting segment for fully contained segments", () => {
      const segmentA: cmath.Vector2 = [2, 6];
      const segmentB: cmath.Vector2 = [3, 5];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toEqual([3, 5]); // Expected: [3, 5]
    });

    it("should return the intersecting segment for touching segments", () => {
      const segmentA: cmath.Vector2 = [1, 5];
      const segmentB: cmath.Vector2 = [5, 10];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toEqual([5, 5]); // Expected: [5, 5]
    });

    it("should return null for non-overlapping segments", () => {
      const segmentA: cmath.Vector2 = [1, 5];
      const segmentB: cmath.Vector2 = [6, 10];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toBeNull(); // Expected: null
    });

    it("should handle zero-length segments that overlap", () => {
      const segmentA: cmath.Vector2 = [3, 3];
      const segmentB: cmath.Vector2 = [3, 5];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toEqual([3, 3]); // Expected: [3, 3]
    });

    it("should return null for zero-length segments that do not overlap", () => {
      const segmentA: cmath.Vector2 = [3, 3];
      const segmentB: cmath.Vector2 = [4, 4];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toBeNull(); // Expected: null
    });

    it("should return the segment itself when two segments are identical", () => {
      const segmentA: cmath.Vector2 = [1, 5];
      const segmentB: cmath.Vector2 = [1, 5];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toEqual([1, 5]); // Expected: [1, 5]
    });

    it("should return null for segments in reverse order that do not overlap", () => {
      const segmentA: cmath.Vector2 = [6, 10];
      const segmentB: cmath.Vector2 = [1, 5];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toBeNull(); // Expected: null
    });

    it("should handle segments where one is fully contained within the other", () => {
      const segmentA: cmath.Vector2 = [1, 10];
      const segmentB: cmath.Vector2 = [3, 7];

      const result = cmath.vector2.intersection(segmentA, segmentB);

      expect(result).toEqual([3, 7]); // Expected: [3, 7]
    });
  });

  describe("distance", () => {
    it("should calculate Euclidean distance between two points", () => {
      const a: cmath.Vector2 = [3, 4];
      const b: cmath.Vector2 = [6, 8];

      const result = cmath.vector2.distance(a, b);

      expect(result).toBe(5); // Expected: 5 (3-4-5 triangle)
    });

    it("should calculate distance for points with negative coordinates", () => {
      const a: cmath.Vector2 = [-1, -2];
      const b: cmath.Vector2 = [2, 1];

      const result = cmath.vector2.distance(a, b);

      expect(result).toBeCloseTo(4.2426, 4); // Expected: sqrt(18) ≈ 4.2426
    });

    it("should return 0 for identical points", () => {
      const a: cmath.Vector2 = [5, 5];
      const b: cmath.Vector2 = [5, 5];

      const result = cmath.vector2.distance(a, b);

      expect(result).toBe(0); // Expected: 0
    });
  });

  describe("lerp", () => {
    it("should interpolate at t=0 (start point)", () => {
      const a: cmath.Vector2 = [0, 0];
      const b: cmath.Vector2 = [10, 20];
      const t = 0;

      const result = cmath.vector2.lerp(a, b, t);

      expect(result).toEqual([0, 0]); // Expected: start point
    });

    it("should interpolate at t=1 (end point)", () => {
      const a: cmath.Vector2 = [0, 0];
      const b: cmath.Vector2 = [10, 20];
      const t = 1;

      const result = cmath.vector2.lerp(a, b, t);

      expect(result).toEqual([10, 20]); // Expected: end point
    });

    it("should interpolate at t=0.5 (midpoint)", () => {
      const a: cmath.Vector2 = [0, 0];
      const b: cmath.Vector2 = [10, 20];
      const t = 0.5;

      const result = cmath.vector2.lerp(a, b, t);

      expect(result).toEqual([5, 10]); // Expected: midpoint
    });

    it("should interpolate at t=0.25 (quarter point)", () => {
      const a: cmath.Vector2 = [0, 0];
      const b: cmath.Vector2 = [100, 50];
      const t = 0.25;

      const result = cmath.vector2.lerp(a, b, t);

      expect(result).toEqual([25, 12.5]); // Expected: quarter point
    });

    it("should handle negative coordinates", () => {
      const a: cmath.Vector2 = [-10, -20];
      const b: cmath.Vector2 = [10, 20];
      const t = 0.5;

      const result = cmath.vector2.lerp(a, b, t);

      expect(result).toEqual([0, 0]); // Expected: midpoint between negative and positive
    });

    it("should handle t values outside [0,1] range", () => {
      const a: cmath.Vector2 = [0, 0];
      const b: cmath.Vector2 = [10, 20];

      const result1 = cmath.vector2.lerp(a, b, -0.5);
      const result2 = cmath.vector2.lerp(a, b, 1.5);

      expect(result1).toEqual([-5, -10]); // Expected: extrapolated before start
      expect(result2).toEqual([15, 30]); // Expected: extrapolated after end
    });

    it("should handle identical start and end points", () => {
      const a: cmath.Vector2 = [5, 5];
      const b: cmath.Vector2 = [5, 5];
      const t = 0.7;

      const result = cmath.vector2.lerp(a, b, t);

      expect(result).toEqual([5, 5]); // Expected: same point regardless of t
    });
  });

  describe("dot", () => {
    it("calculates dot product of basic vectors", () => {
      const a: cmath.Vector2 = [3, 4];
      const b: cmath.Vector2 = [1, 2];
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(11); // 3*1 + 4*2 = 11
    });

    it("calculates dot product with negative components", () => {
      const a: cmath.Vector2 = [-2, 3];
      const b: cmath.Vector2 = [4, -1];
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(-11); // (-2)*4 + 3*(-1) = -8 - 3 = -11
    });

    it("returns 0 for perpendicular vectors", () => {
      const a: cmath.Vector2 = [1, 0];
      const b: cmath.Vector2 = [0, 1];
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(0);
    });

    it("returns 0 for perpendicular vectors with negative components", () => {
      const a: cmath.Vector2 = [1, 1];
      const b: cmath.Vector2 = [1, -1];
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(0); // 1*1 + 1*(-1) = 0
    });

    it("calculates dot product of parallel vectors", () => {
      const a: cmath.Vector2 = [2, 3];
      const b: cmath.Vector2 = [4, 6]; // 2 * [2, 3]
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(26); // 2*4 + 3*6 = 8 + 18 = 26
    });

    it("calculates dot product of anti-parallel vectors", () => {
      const a: cmath.Vector2 = [2, 3];
      const b: cmath.Vector2 = [-4, -6]; // -2 * [2, 3]
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(-26); // 2*(-4) + 3*(-6) = -8 - 18 = -26
    });

    it("returns 0 for zero vector", () => {
      const a: cmath.Vector2 = [0, 0];
      const b: cmath.Vector2 = [3, 4];
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(0);
    });

    it("calculates dot product of identical vectors", () => {
      const a: cmath.Vector2 = [3, 4];
      const b: cmath.Vector2 = [3, 4];
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(25); // 3*3 + 4*4 = 9 + 16 = 25 (squared magnitude)
    });

    it("handles unit vectors", () => {
      const a: cmath.Vector2 = [1, 0];
      const b: cmath.Vector2 = [1, 0];
      const result = cmath.vector2.dot(a, b);
      expect(result).toBe(1); // Unit vector dot product with itself
    });
  });

  describe("cross", () => {
    it("calculates cross product of basic vectors", () => {
      const a: cmath.Vector2 = [3, 4];
      const b: cmath.Vector2 = [1, 2];
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(2); // 3*2 - 4*1 = 6 - 4 = 2
    });

    it("calculates cross product with negative components", () => {
      const a: cmath.Vector2 = [-2, 3];
      const b: cmath.Vector2 = [4, -1];
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(-10); // (-2)*(-1) - 3*4 = 2 - 12 = -10
    });

    it("returns 0 for parallel vectors", () => {
      const a: cmath.Vector2 = [2, 4];
      const b: cmath.Vector2 = [1, 2]; // parallel to a (b = 0.5 * a)
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(0);
    });

    it("returns 0 for anti-parallel vectors", () => {
      const a: cmath.Vector2 = [2, 4];
      const b: cmath.Vector2 = [-1, -2]; // anti-parallel to a (b = -0.5 * a)
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(0);
    });

    it("calculates cross product of perpendicular vectors", () => {
      const a: cmath.Vector2 = [1, 0];
      const b: cmath.Vector2 = [0, 1];
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(1); // 1*1 - 0*0 = 1
    });

    it("calculates cross product of perpendicular vectors (negative)", () => {
      const a: cmath.Vector2 = [0, 1];
      const b: cmath.Vector2 = [1, 0];
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(-1); // 0*0 - 1*1 = -1
    });

    it("returns 0 for zero vector", () => {
      const a: cmath.Vector2 = [0, 0];
      const b: cmath.Vector2 = [3, 4];
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(0);
    });

    it("calculates cross product of identical vectors", () => {
      const a: cmath.Vector2 = [3, 4];
      const b: cmath.Vector2 = [3, 4];
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(0); // 3*4 - 4*3 = 0
    });

    it("handles unit vectors", () => {
      const a: cmath.Vector2 = [1, 0];
      const b: cmath.Vector2 = [0, 1];
      const result = cmath.vector2.cross(a, b);
      expect(result).toBe(1); // Unit vectors cross product
    });

    it("demonstrates orientation (counter-clockwise)", () => {
      const a: cmath.Vector2 = [1, 0]; // right
      const b: cmath.Vector2 = [0, 1]; // up
      const result = cmath.vector2.cross(a, b);
      expect(result).toBeGreaterThan(0); // Positive = counter-clockwise
    });

    it("demonstrates orientation (clockwise)", () => {
      const a: cmath.Vector2 = [0, 1]; // up
      const b: cmath.Vector2 = [1, 0]; // right
      const result = cmath.vector2.cross(a, b);
      expect(result).toBeLessThan(0); // Negative = clockwise
    });
  });

  describe("project", () => {
    it("projects vector onto x-axis", () => {
      const vector: cmath.Vector2 = [3, 4];
      const axis: cmath.Vector2 = [1, 0];
      const result = cmath.vector2.project(vector, axis);
      expect(result).toEqual([3, 0]);
    });

    it("projects vector onto y-axis", () => {
      const vector: cmath.Vector2 = [3, 4];
      const axis: cmath.Vector2 = [0, 1];
      const result = cmath.vector2.project(vector, axis);
      expect(result).toEqual([0, 4]);
    });

    it("projects vector onto diagonal axis", () => {
      const vector: cmath.Vector2 = [2, 3];
      const axis: cmath.Vector2 = [1, 1];
      const result = cmath.vector2.project(vector, axis);
      expect(result[0]).toBeCloseTo(2.5);
      expect(result[1]).toBeCloseTo(2.5);
    });

    it("handles zero axis vector", () => {
      const vector: cmath.Vector2 = [3, 4];
      const axis: cmath.Vector2 = [0, 0];
      const result = cmath.vector2.project(vector, axis);
      expect(result).toEqual([0, 0]);
    });

    it("handles perpendicular vectors", () => {
      const vector: cmath.Vector2 = [0, 5];
      const axis: cmath.Vector2 = [1, 0];
      const result = cmath.vector2.project(vector, axis);
      expect(result).toEqual([0, 0]);
    });

    it("handles parallel vectors", () => {
      const vector: cmath.Vector2 = [6, 8];
      const axis: cmath.Vector2 = [3, 4];
      const result = cmath.vector2.project(vector, axis);
      expect(result[0]).toBeCloseTo(6);
      expect(result[1]).toBeCloseTo(8);
    });
  });
});
